package com.gitee.wsl.ext.string.builder


internal const val EIGHT_BIT_START = 127.toChar()

/**
 * Escapes special characters of an Appendable for HTML output. Handles double quotes,
 * greater than, less than, ampersand, and characters greater than eight bit.
 * @param csq A sequence of characters to HTML escape.
 */
fun Appendable.escapeHTML(csq: CharSequence) {
    csq.forEach { c->
        when {
            c > EIGHT_BIT_START || c == '"' || c == '<' || c == '>' || c == '&' -> {
                append("&#")
                append(c.code.toString())
                append(';')
            }
            else -> append(c)
        }
    }
}

fun Appendable.toAttributeName(csq: CharSequence) {
    csq.forEach {
        if (it in 'A'..'Z') {
            append('-')
            append(it.lowercaseChar())
        } else {
            append(it)
        }
    }
}

/**
 * Emit a string value, optionally making it literal and/or multiline.
 *
 * @param string
 * @param isLiteral Whether the string is literal (single-quotes).
 * @param isMultiline Whether the string is multiline.
 * @return this instance
 */
fun Appendable.emitValue(
    string: String,
    isLiteral: Boolean = false,
    isMultiline: Boolean = false
): Appendable =
    if (isMultiline) {
        val quotes = if (isLiteral) "'''" else "\"\"\""

        append(quotes)
            .appendLine()
            .append(string)
            .appendLine()
            .append(quotes)
    } else {
        val quote = if (isLiteral) '\'' else '"'

        append(quote)
            .append(string)
            .append(quote)
    }

fun Appendable.dot() = append('.')

fun Appendable.comment(comment: String, inline: Boolean = false) = append(if (inline) " # " else "# ").append(comment)

/**
 * Emits a [key] as a quoted key, optionally making it literal (single-quotes).
 *
 * @param key
 * @param isLiteral Whether the key should be emitted as a literal string
 * (single-quotes).
 * @return `this`
 */
fun Appendable.emitQuotedKey(key: String, isLiteral: Boolean = false) =
    emitValue(string = key, isLiteral)

fun Appendable.whitespace(count: Int = 1) = repeat(count) { append(' ') }


/**
 * Emits an integer value, optionally changing its representation from decimal.
 *
 * @param integer
 * @param representation How the integer will be represented in TOML.
 * @param groupSize The digit group size, or less than `1` for no grouping. For
 * example, a group size of `3` emits `1_000_000`, `4` emits `0b1111_1111`, etc.
 * @return this instance
 */
@Suppress("SAY_NO_TO_VAR")
public fun Appendable.emitValue(
    integer: Long,
    representation: IntegerRepresentation = IntegerRepresentation.DECIMAL,
    groupSize: Int = 0
): Appendable {
    // Todo: Add groupSize to the annotation and AST and remove GROUPED.
    if (representation == IntegerRepresentation.GROUPED) {
        return emitValue(integer, representation = IntegerRepresentation.DECIMAL, groupSize = 3)
    }

    if (integer < 0) {
        append('-')
    }

    var digits = integer.toString(representation.radix).trimStart('-')

    if (groupSize > 0) {
        digits = (digits as CharSequence).reversed()
            .chunked(groupSize, CharSequence::reversed)
            .asReversed()
            .joinToString(separator = "_")
    }

    return append(representation.prefix).append(digits)
}

/**
 * How a TOML integer should be represented during encoding.
 *
 * @property prefix The prefix, if any, signalling this representation in TOML.
 * @property radix The radix or base number of the representation.
 */
@Suppress("MAGIC_NUMBER")
enum class IntegerRepresentation(
    val prefix: String = "",
    val radix: Int = 10,
) {
    /**
     * A binary number prefixed with `0b`.
     */
    BINARY("0b", 2),

    /**
     * A decimal number.
     */
    DECIMAL,

    /**
     * A grouped decimal number, such as `1_000_000`.
     */
    GROUPED,

    /**
     * A hexadecimal number prefixed with `0x`.
     */
    HEX("0x", 16),

    /**
     * An octal number prefixed with `0o`.
     */
    OCTAL("0o", 8),
    ;
}

infix operator fun Appendable.plusAssign(char: Char) {
    append(char)
}

infix operator fun Appendable.plusAssign(charSequence: CharSequence) {
    append(charSequence)
}

